package com.cctc.juc.source.Bitc.Ajmm;
/**
 * 【面试题】
 * JMM（Java 内存模型）是什么？
 * 概述：JMM 主要用于规范和指引 Java 线程在不同的 CPU、内存架构和操作系统之间的确定性行为 - 即一个线程所做的变动能够立即被其他线程可见。它为 Java 虚拟机提供了合理禁用 CPU 缓存和禁止 CPU 指令重排序的方法，从而保证多线程环境下共享变量的内存可见性和 CPU 指令并发执行的有序性。
 * 为了屏蔽 Java 线程在不同 CPU、内存架构与操作系统之间的访问差异，JMM（Java 内存模型）规定：
 * 1）所有变量都存储在主存上；
 * 2）每个线程都有自己的工作内存，当线程使用变量时，会从主存中复制一份变量副本到自己的工作内存中，操作完成同步到主存；
 * 3）各线程之间无法直接访问彼此工作内存中的变量，只能通过主存来进行数据传递。
 * 为此，JMM（Java 内存模型）定义了一套内存操作的抽象指令集（读读、写写、读写、写读内存屏障），然后将这些抽象指令集包含到 Java 的 volatile、final、synchronized 等关键字的语义中，要求 JVM 在实现这个关键字时必须具备其包含的抽象指令集的能力，从而保证多线程环境下共享变量的内存可见性以及 CPU 指令并发执行的有序性。
 * - 读读屏障（性能较高）：读读之间插入读读屏障，只需要保证前后读指令的有序性，因此性能较高；
 * - 写写屏障（性能较高）：写写之间插入写写屏障，只需要保证前后写指令的有序性，因此性能较高；
 * - 读写屏障（性能较低）：前读后写之间插入读写屏障，需要保证前读的内存可见性和前读后写的指令有序性，因此性能较低；
 * - 写读屏障（性能最低）：前写后读之间插入写读屏障，保证前写的内存可见性和前写后读指令有序性，因为前写操作需要基于 MESI 协议维护 CPU 缓存的一致性，因此性能最低。
 * 例如，对于使用 volatile 关键字修饰的变量，JVM 会：
 * - 在 volatile 变量的读操作前后分别插入一个读读屏障、读写屏障；
 * - 在 volatile 变量的写操作前后分别插入一个写写屏障、写读屏障；
 * 从而保障 volatile 变量的内存可见性和指令有序性。
 * 由于 JMM（Java 内存模型）抽象指令集（读读、写写、读写、写读内存屏障）是面向 JVM 的，对 Java 工程师来说是透明的。为此，JMM（Java 内存模型） 还定义了一套  Happens-Before（先行发生）规则，只要两个 Java 语句之间存在 Happens-Before（先行发生）关系，JMM（Java 内存模型）就会尽可能的确保这两个 Java 语句之间的内存可见性和指令有序性，这些规则使得程序员在并发编程时思路更加清晰，更容易理清并发程序执行时的顺序。
 * 例如：
 * - 程序次序规则：线程内的代码能够按照先后顺序进行执行；
 * - 管理锁定规则：对于同一个锁来说，前一个线程的解锁操作一定要先行发生于后一个线程的加锁操作。
 * - volatile 变量规则：前一个线程对 volatile 变量的写操作一定要先行发生于后一个线程对 volatile 的读操作；
 * - 线程启动规则：对一个线程的启动操作必须先行发生于对该线程的其他任何操作；
 * - 线程终结规则：对一个线程的其他任何操作必须先行发生于对该线程的终结操作；
 * - 对象终结规则：对一个对象的初始化构造操作必须先行发生于对该对象的终结操作。
 * - final 变量规则：
 * 1）对一个 final 所 “引用” 对象的初始化操作必须先行发生于对该 “引用” 的赋值操作，以避免线程访问到尚未初始化的 “引用” 对象而产生错误；
 * 2）对一个 final “引用” 的初次读取操作必须先行发生于对它所 “引用” 对象的初次访问操作，以避免线程访问到不正确的 “引用” 对象而产生错误。
 * 从而确保并发环境下 final “引用” 变量的内存可见性和指令有序性。
 * 补充知识点：
 * 注意：
 * - volatile 变量不具备原子性；
 * --> 而 synchronized 通过监视器锁（moniterenter、moniterexit 指令）控制一个变量同一时刻只允许被一个线程（互斥）操作，既能保证内存可见性，又能保证原子性。
 * - 而且由于在 volatile 变量读写前后插入了一系列内存屏障，性能开销比较大，尤其是写操作后面插入的写读屏障，为了维护 CPU 缓存的一致性，可谓不惜一切代价，这就意味着对 volatile 变量的每次写入操作，性能是非常低的。为此，我们可以使用 Unsafe.putXXXVolatile() 或者 Unsafe.getXXXVolatile() 方法，直接基于变量的内存地址进行读写操作，直接实现 volatile 语义；或者对于不需要保证写入立即可见的 volatile 变量，可以使用 Unsafe.putOrderXXX() 方法写入，只保障写入操作的有序性即可，这种做法在很多著名的中间件（例如 Netty、JCTools 等）中，经常被用作提升 volatile 关键字的替选方案。
 * > JCTools（Java Concurrency Tools）：提供了一系列非阻塞的并发数据结构，旨在弥补 JUC 的不足。当存在线程争抢时，非阻塞的并发数据结构要比阻塞式的并发数据结构提供更好的性能。
 * --------------------------------------------------------------------------------------
 * 提示：下面是一些背景知识，可以尽情发挥。
 * --------------------------------------------------------------------------------------
 * 【背景知识】
 * 为了提升计算机的整体性能，计算机生产厂商们共同采取了以下优化措施：
 * 1）为了缓解 CPU 计算速度与内存存取速度之间的差异，CPU 增加了三级缓存；
 * 3）为了提升 CPU 指令的执行效率，编译系统优化了 CPU 指令的执行顺序。
 * 2）为了提升进程调度的性能，操作系统引入了多线程（轻量级）模型；
 * 以上优化措施，在无形当中造成了很多并发编程的问题，其中最为核心的就是可见性、有序性和原子性问题：
 * 1）可见性：一个线程对共享变量的修改，另一个线程能够立即看到，CPU 缓存带来了可见性问题；
 * 2）有序性：指的是程序按照代码既定的顺序执行，编译优化带来了有序性问题；
 * 3）原子性：一个或多个操作在 CPU 中执行的过程不被中断，线程切换带来了原子性问题。
 * 1. 计算机体系结构之 CPU 物理缓存结构
 * CPU 物理（三级）缓存（标准）结构：一台计算机往往有多个 CPU 插槽，每个 CPU 插槽中有多个 CPU 核心。每个 CPU 核心（含寄存器）中各自存在一个 L1 和 L2 两级高速缓存；每个 CPU 插槽中一般存在一个 L3 三级高速缓存，由各自插槽内的多个 CPU 核心共享；主存则由全部插槽上的所有 CPU 核共享。
 * CPU 通过（三级）缓存缓解了 CPU 计算速度与内存存取速度之间的差异，主要有以下两点优势：
 * 1）写缓存可以保证 CPU 指令流水线持续运行，可以避免由于 CPU 停顿下来等待向主存中写入数据而产生的延迟；
 * 2）通过批处理的方式刷新缓存，以及合并缓存中对同一主存的多次写入，减少对内存总线的占用。
 * 然而，在并发环境下，由于每个线程可能会运行在不同的 CPU 核心上，导致在不同 CPU 内核中运行的线程看到同一个变量的缓存是不一样的，就会存在内存可见性问题。
 * 为了解决内存可见性问题，CPU 提供了两种同步方法：总线锁和缓存一致性协议。
 * 1）总线锁：通过在总线上发出一个 LOCK# 信号，锁定期间只允许一个 CPU 内核线程读取共享内存中的数据，达到互斥访问的目的。
 * --> 缺点就是在多 CPU 架构中，某一个 CPU 访问主存时，总线锁把 CPU 和主存的通信给锁住了，其他 CPU 在此期间不能操作其他内存地址的数据，开销比较大。
 * 2）缓存一致性协议：总线锁的锁粒度太大，为了以更高的性能解决缓存一致性问题，最好的方法就是控制锁的保护粒度，即只需要保证对于被多个 CPU 缓存的同一份数据的可见性即可。为此，CPU 引入了缓存一致性协议，约束各个 CPU 在存取数据时根据协议来操作，即当某个 CPU 对缓存中的数据进行操作之后，需要立即通知其他 CPU 放弃存储在它们内部的缓存数据，或者从主存中重新读取，从而达到数据访问的一致性，其中最常用的就是 MESI 写入失效协议及 RFO 失效请求。
 * --> MESI 协议要求在每个缓存行上维护两个状态位，使得每个缓存行可能处于 M、E、S、I 四种状态之一：
 * 1）Modified（修改）：该缓存行只存在于本 CPU 内，且与主存中的数据不一致，被修改了；
 * 2）Exclusive（独占）：该缓存行只存在于本 CPU 内，且被修改的数据已经同步到主存内了；
 * 3）Shared（共享）：该缓存行的数据被其他 CPU 共享了，且与主存一致；
 * 4）Invalid（失效）：该缓存行被其中一个 CPU 修改了，该 CPU 需要向其他共享此缓存行的 CPU 发送 RFO 失效请求，通知其他 CPU 将其缓存行置为 Invalid（失效）状态。
 * MESI 协议带来的性能问题：各个 CPU 的缓存行所处的状态是通过发送通知消息来进行同步的，同步期间将导致 CPU 内核在此期间无事可做，甚至一旦某个 CPU 内核发生阻塞，将导致其他 CPU 内核也处于阻塞状态，从而带来极大的性能损耗。
 * 解决办法：同步变异步。CPU 缓存数据一致性和 Redis 缓存数据一致性以及 RocketMQ 主从同步、MySQL 主从同步等分布式节点之间的数据一致性，在原理上基本都是相通的，即同步通信数据强一致，但性能低，而异步通信性能高，但数据是弱一致性或最终一致性的。
 * 对于 MESI 协议来说，CPU 内部引入了两个组件：存储缓存（Store Buffer）和失效队列（Invalidate Queue）。
 * 1）存储缓存（Store Buffer）是缓存写入方用到的组件，将同步流程 [缓存写入方准备将计算结果写入 Shared 状态的缓存行 -> 向其他共享此缓存行的 CPU 发送 RFO 失效请求 -> 收到所有 Ack 失效确认 -> 将数据写入本地缓存行，并把缓存行状态修改位 Modified] 修改为异步操作，先写入存储缓存（Store Buffer），然后执行异步操作，待其他所有 CPU 返回 Ack 失效确认后再写入缓存行。
 * 2）失效队列（Invalidate Queue）是缓存失效方用到的组件，将同步流程 [CPU 收到 RFO 失效请求 -> 把缓存行状态修改位 Invalid -> 回复 Ack 失效确认] 修改为异步操作，将收到的 RFO 失效请求放入失效队列（Invalidate Queue），然后立即回复  Ack（响应结果）失效确认，再异步更新缓存行的状态。
 * 2. 编译系统之 CPU 指令重排序
 * 编译器或者解释器为了优化程序的执行性能，有时会改变没有前后依赖关系的程序的执行顺序。但是不管怎么重排序，都需要保证最终的执行结果是不变的。
 * 3. 操作系统之多线程模型
 * 多线程模型是基于高并发调度诉求从进程内部演化而来的。
 * - 线程是进程 “程序段” 的一次顺序执行；
 * - 一个进程可以有一个或多个线程，各个线程之间共享进程的内存空间（方法区内存、堆内存）、系统资源（文件句柄、系统信号）等。
 * 因此，线程又称为轻量级进程，它的出现既能充分发挥 CPU 的性能，又弥补了进程调度过于笨重的问题。
 * 说了这么多，我们到底该如何解决并发编程中的可见性、有序性和原子性问题呢？？？
 * 1）按需禁用 CPU 缓存和编译优化（禁止 CPU 指令重排序）解决可见性、有序性问题 -> [JMM（Java Memory Model，Java 内存模型）] -> [volatile、final 关键字]；
 * 2）使用原子（互斥）操作解决原子性问题 -> CAS 原子类、AQS（抽象队列同步器）、synchronized 关键字、Lock 锁、同步组件、并发容器等。
 */